<?php namespace App\Core\Metadata;

class Validator {
    
    public function validate($data, $rules){
        $res = [
            'status' => true,
            'errors' => []
        ];
        foreach($rules as $field => $rule){
            if(!isset($data[$field])){
                $res['status'] = false;
                $res['errors'][] = [$field, '没有接收到值'];
                continue;
            }
            if(!$this->validateItem($data[$field], $rule)){
                $res['status'] = false;
                $res['errors'][] = [$field, '格式有误'];
            }
        }
        return $res;
    }
    
    public function str($value, $range = []){
        if($range){
            return $this->isInRange(strlen($value), $range);
        }
        return true;
    }
    
    public function numeric($value, $range = []){
        if(!is_numeric($value)){
            return false;
        }
        if($range){
            return $this->isInRange(strlen($value), $range);
        }
        return true;
    }
    
    public function integer($value, $range = []){
        if(!is_numeric($value)){
            return false;
        }
        if($range){
            return $this->isInRange($value, $range);
        }
        return true;
    }
    
    public function text($value, $range = []){
        if($range){
            return $this->isInRange(strlen($value), $range);
        }
        return true;
    }
    
    public function html($value, $range = []){
        if($range){
            return $this->isInRange(strlen($value), $range);
        }
        return true;
    }
    
    public function json($value, $range = null, $json = null, $itemRule = null){
        $valueArr = json_decode($value, true);
        if(is_null($valueArr)){
            return false;
        }
        if($json){
            foreach($json as $key => $rule){
                if(!isset($valueArr[$key])){
                    return false;
                }
                if(!$this->validateItem($valueArr[$key], $rule)){
                    return false;
                }
            }
        }
        if($itemRule){
            foreach($valueArr as $item){
                if(!$this->validateItem($item, $itemRule)){
                    return false;
                }
            }
        }
        if($range){
            return $this->isInRange(count($valueArr), $range);
        }
        return true;
    }
    
    public function in($value, $in = []){
        if($in){
            return in_array($value, $in);
        }
        return true;
    }
    
    public function split($value, $range = null, $itemRule = null){
        $valueArr = explode(',', $value);
        if($itemRule){
            foreach($valueArr as $item){
                if(!$this->validateItem($item, $itemRule)){
                    return false;
                }
            }
        }
        if($range){
            return $this->isInRange(count($valueArr), $range);
        }
        return true;
    }
    
    public function regexp($value, $pattern = ''){
        if(!$pattern){
            throw new \Exception('parameter not validated');
        }
        return preg_match($pattern, $value);
    }
    
    public function phone($value){
        return $this->regexp($value, '/^1\d{10}$/');
    }
    
    public function email($value){
        return $this->regexp($value, '/^\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}$/');
    }
    
    public function url($value){
        return $this->regexp($value, '~^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?~i');
    }
    
    public function unique($model, $field, $value, $ignore = false, $ignoreField = 'id'){
        if(empty($value)){
            return false;
        }
        $model->where($field, '=', $value);
        if($ignore){
            $model->where($ignoreField, '!=', $ignore);
        }
        return $model->count() == 0;
    }
    
    protected function validateItem($value, $rule){
        switch ($rule['type']) {
            case 'str' :
            case 'string' :
                $range = isset($rule['range']) ? $rule['range'] : null;
                return $this->str($value, $range);
            case 'num' :
            case 'number' :
            case 'numeric' :
                $range = isset($rule['range']) ? $rule['range'] : null;
                return $this->numeric($value, $range);
            case 'int' :
            case 'integer' :
                $range = isset($rule['range']) ? $rule['range'] : null;
                return $this->integer($value, $range);
            case 'txt' :
            case 'text' :
                $range = isset($rule['range']) ? $rule['range'] : null;
                return $this->text($value, $range);
            case 'html' :
                $range = isset($rule['range']) ? $rule['range'] : null;
                return $this->html($value, $range);
            case 'json' :
                $range = isset($rule['range']) ? $rule['range'] : null;
                $json = isset($rule['json']) ? $rule['json'] : null;
                $item = isset($rule['item']) ? $rule['item'] : null;
                return $this->json($value, $range, $json, $item);
            case 'in' :
                $in = isset($rule['in']) ? $rule['in'] : [];
                return $this->in($value, $in);
            case 'split' :
                $range = isset($rule['range']) ? $rule['range'] : null;
                $item = isset($rule['item']) ? $rule['item'] : null;
                return $this->split($value, $range, $item);
            case 'regexp' :
                $pattern = isset($rule['pattern']) ? $rule['pattern'] : null;
                return $this->regexp($value, $pattern);
            default :
                return $this->{$rule['type']}($value);
        }
    }
    
    protected function isInRange($value, $range){
        if(!is_array($range)){
            throw new RuntimeError('Matadata format error.Range is not validated.');
        }
        if(count($range) == 1){
            $range[1] = null;
        }
        if(($range[0] != '' || !is_null($range[0])) && $value < $range[0]){
            return false;
        }
        if(($range[1] != '' || !is_null($range[1])) && $value > $range[1]){
            return false;
        }
        return true;
    }
}